/* 
 * Copyright (C) 2004-2005 Jonathan Bindel
 * Copyright (C) 2006-2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

using System;
using System.Net;

using DCSharp.Backend.Managers;
using DCSharp.Backend.Objects;
using DCSharp.Backend.Protocols;
using DCSharp.Logging;

using Nmdc = DCSharp.Backend.Protocols.Nmdc;

namespace DCSharp.Backend.Connections
{
	public class MessageEventArgs : EventArgs
	{
		private string message;
		private Identity from;
		private Identity to;

		public MessageEventArgs(Identity from, Identity to, string message)
		{
			this.message = message;
			this.from = from;
			this.to = to;
		}

		public string Message
		{
			get { return message; }
		}

		public Identity From
		{
			get { return from; }
		}

		public Identity To
		{
			get { return to; }
		}
	}

	public class RedirectedEventArgs : EventArgs
	{
		private Uri target;

		public RedirectedEventArgs(Uri target)
		{
			this.target = target;
		}

		public Uri Target
		{
			get { return target; }
		}
	}

	/// <summary>
	/// A connection to a hub.
	/// </summary>
	/// <remarks>
	/// The only implemented protocol is Nmdc. Support for Adc is on its way.
	/// </remarks>
	public class HubConnection : Connection
	{
		public event EventHandler NameChanged;
		public event EventHandler TopicChanged;
		public event EventHandler<MessageEventArgs> Message;
		public event EventHandler<MessageEventArgs> PrivateMessage;
		public event EventHandler<RedirectedEventArgs> Redirected;

		private static Logger log = LogManager.GetLogger("HubConnection");

		private Uri uri;
		private string address;
		private LocalIdentity localIdentity;
		private string password;

		private string name;
		private string topic;

		private UserManager users;

		#region Constructors

		/// <summary>
		/// Initializes a new <see cref="HubConnection"/>.
		/// </summary>
		/// <param name="uri">The <see cref="Uri"/> of the hub.</param>
		/// <param name="localIdentity">The identity of the local user.</param>
		/// <param name="helper">A <see cref="IHubProtocolHelper"/>.</param>
		public HubConnection(Uri uri, LocalIdentity localIdentity,
			IHubProtocolHelper helper) : base()
		{
			if (uri == null)
			{
				throw new ArgumentNullException("uri");
			}
			if (localIdentity == null)
			{
				throw new ArgumentNullException("localIdentity");
			}
			if (helper == null)
			{
				throw new ArgumentNullException("helper");
			}
			this.uri = uri;
			this.localIdentity = localIdentity;

			// Protocol detection
			if (uri.Scheme == "dchub")
			{
				Protocol = new Nmdc.HubProtocol(this, helper);
			}
			else
			{
				throw new ArgumentException("Unknown URI scheme.", "uri");
			}

			users = new UserManager(this);
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets the local identity for this hub.
		/// </summary>
		/// <value>The local identity for this hub.</value>
		public LocalIdentity LocalIdentity
		{
			get { return localIdentity; }
		}

		/// <summary>
		/// Gets or set the password for this hub.
		/// </summary>
		/// <value>The password for this hub.</value>
		public string Password
		{
			get { return password; }
			set { password = value; }
		}

		/// <summary>
		/// Gets or set the name of the hub.
		/// </summary>
		/// <value>The name of the hub.</value>
		public string Name
		{
			get { return name; }
			internal set
			{
				if (value != name)
				{
					name = value;
					OnNameChanged();
				}
			}
		}

		public string Topic
		{
			get { return topic; }
			set
			{
				if (value != topic)
				{
					topic = value;
					OnTopicChanged();
				}
			}
		}

		/// <summary>
		/// Gets the address to the hub.
		/// </summary>
		/// <value>The address to the hub.</value>
		/// <remarks>The address is only available after connecting to the hub.</remarks>
		public string Address
		{
			get { return address; }
		}

		/// <summary>
		/// Gets or set the hub URI.
		/// </summary>
		/// <value>The hub URI.</value>
		public Uri Uri
		{
			get { return uri; }
		}

		/// <value>
		/// The users on the hub.
		/// </value>
		public UserManager Users
		{
			get { return users; }
		}

		protected IHubProtocol HubProtocol
		{
			get { return (IHubProtocol)Protocol; }
		}

		#endregion

		#region Methods

		#region Connect

		/// <summary>
		/// Begins an async connection attempt.
		/// </summary>
		public override void Connect()
		{
			State = ConnectionState.Connecting;
			try
			{
				Dns.BeginGetHostEntry(uri.Host, new AsyncCallback(HostEntryReceived),
					null);
			}
			catch (Exception e)
			{
				OnError(ConnectionError.Dns, e);
				Disconnect();
			}
		}

		private void HostEntryReceived(IAsyncResult ar)
		{
			IPHostEntry ipHostEntry;
			try
			{
				ipHostEntry = Dns.EndGetHostEntry(ar);
				IPAddress ip = ipHostEntry.AddressList[0];

				int port = uri.Port;
				if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
				{
					port = 411;
				}

				EndPoint = new IPEndPoint(ip, port);
				this.address = EndPoint.ToString();
			}
			catch (Exception e)
			{
				OnError(ConnectionError.Dns, e);
				Disconnect();
				return;
			}

			base.Connect();
		}

		#endregion

		#region Send/Receive

		internal override void Send(string message, System.Text.Encoding encoding)
		{
			base.Send(message, encoding);
			log.Debug("Sent: " + message, null, this);
		}

		protected override void HandleMessage(string message)
		{
			log.Debug("Received: " + message, null, this);
			base.HandleMessage(message);
		}

		#endregion

		/// <summary>
		/// Requests a <see cref="UserConnection"/> to a <see cref="User"/>.
		/// </summary>
		/// <param name="user">The <see cref="User"/> the request should be made to</param>
		public void RequestConnection(User user)
		{
			this.HubProtocol.RequestConnection(user);
		}

		/// <summary>
		/// Sends a search resuest to the users on the hub.
		/// </summary>
		/// <param name="searchInfo">The search criteria.</param>
		public void Search(SearchInfo searchInfo)
		{
			this.HubProtocol.Search(searchInfo);
		}

		/// <summary>
		/// Sends a message to all the users on the hub.
		/// </summary>
		/// <param name="message">The message to send.</param>
		public void SendMessage(string message)
		{
			this.HubProtocol.SendMessage(message);
		}

		/// <summary>
		/// Sends a message to a <see cref="User"/> on the hub.
		/// </summary>
		/// <param name="user">The <see cref="User"/> the message is to.</param>
		/// <param name="message">The message to send.</param>
		public void SendPrivateMessage(User user, string message)
		{
			this.HubProtocol.SendPrivateMessage(user, message);
		}

		public bool Equals(HubConnection hub)
		{
			if (this == hub || this.Uri == hub.Uri ||
				(this.Address != null && this.Address == hub.Address &&
				this.Uri.Port == hub.Uri.Port))
			{
				return true;
			}
			return false;
		}

		public override string ToString()
		{
			return uri.ToString();
		}

		protected override void OnError(ConnectionError error, Exception e)
		{
			log.Error(error.ToString(), e, this);
			base.OnError(error, e);
		}

		protected override void OnStateChanged()
		{
			log.Info(State.ToString(), null, this);
			base.OnStateChanged();
		}

		protected virtual void OnNameChanged()
		{
			if (NameChanged != null)
			{
				NameChanged(this, EventArgs.Empty);
			}
		}

		protected virtual void OnTopicChanged()
		{
			if (TopicChanged != null)
			{
				TopicChanged(this, EventArgs.Empty);
			}
		}

		protected internal virtual void OnMessage(Identity from, string message)
		{
			if (Message != null)
			{
				Message(this, new MessageEventArgs(from, null, message));
			}
		}

		protected internal virtual void OnPrivateMessage(Identity from, Identity to,
			string message)
		{
			if (PrivateMessage != null)
			{
				PrivateMessage(this, new MessageEventArgs(from, to, message));
			}
		}

		protected internal virtual void OnRedirected(Uri target)
		{
			if (Redirected != null)
			{
				Redirected(this, new RedirectedEventArgs(target));
			}
		}

		#region IDisposable Members

		protected override void Dispose(bool disposing)
		{
			users.Clear();
			base.Dispose(disposing);
		}

		#endregion

		#endregion
	}
}
